package com.codewsq.java;

import org.junit.Test;

import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;

/**
 * 反射功能小试
 *
 * @author codewsq
 * @date 2021/7/17 0017 16:15
 */
public class ReflectionTest {

    /*
     * 一般创建类的对象时，使用new的方式
     * */
    @Test
    public void test() {
        Person person = new Person();
        // 调用类的属性
        person.age = 10;
        System.out.println(person);
        person.show(); // 调用类的方法

        // 类中私有的属性方法不能调用
    }

    /*
     * 使用反射的方式创建对象
     * */
    @Test
    public void test1() throws Exception {
        Class<Person> clazz = Person.class;

        // 1. 通过反射创建类的对象
        Constructor constructor = clazz.getConstructor(String.class, int.class);
        Object o = constructor.newInstance("Tom", 20);
        Person person = (Person) o;
        System.out.println(person.toString());
        System.out.println("*************************************");
        // 2. 通过反射调用对象的属性和方法
        // 调用属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(person, "jerry");
        System.out.println(person);

        // 调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.setAccessible(true);
        show.invoke(person);
        System.out.println("*************************************");

        // 3. 通过反射可以调用类中私有的构造器，属性，方法
        // 私有的构造器
        Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
        declaredConstructor.setAccessible(true);
        Person jack = declaredConstructor.newInstance("jack");
        System.out.println(jack);

        // 私有的属性
        Field declaredField = clazz.getDeclaredField("name");
        declaredField.setAccessible(true);
        declaredField.set(person, "李四");// 给 属性赋值 参数1：调用的对象 参数2：属性值
        System.out.println();

        // 私有的方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        Object invoke = showNation.invoke(person, "中国");
        System.out.println(invoke);
    }

    //疑问1：通过直接new的方式或反射的方式都可以调用公共的结构，开发中到底用那个？
    //建议：直接new的方式。
    //什么时候会使用：反射的方式。 反射的特征：动态性
    //疑问2：反射机制与面向对象中的封装性是不是矛盾的？如何看待两个技术？
    //不矛盾。

    /*
    关于java.lang.Class类的理解
    1.类的加载过程：
    程序经过javac.exe命令以后，会生成一个或多个字节码文件(.class结尾)。
    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
    加载到内存中。此过程就称为类的加载。加载到内存中的类，我们就称为运行时类，此
    运行时类，就作为Class的一个实例。

    2.换句话说，Class的实例就对应着一个运行时类。
    3.加载到内存中的运行时类，会缓存一定的时间。在此时间之内，我们可以通过不同的方式
    来获取此运行时类。
     */

    //获取Class的实例的方式（前三种方式需要掌握）
    @Test
    public void test2() throws ClassNotFoundException {
        // 第一种方式，直接通过运行时类的名称 .class
        Class<Person> clazz = Person.class;
        System.out.println(clazz);

        // 第二种方式：通过运行时类实例化的对象调用getClass()
        Person person = new Person();
        Class<? extends Person> aClass = person.getClass();
        System.out.println(aClass);

        // 第三种方式：调用Class类的静态方法：forName(String classPath) -- 常用
        Class<?> forName = Class.forName("com.codewsq.java.Person");
        System.out.println(forName);


        // 第四种方式:使用类加载器 ClassLoader(了解)
        // 方法1.
//        ClassLoader classLoader = this.getClass().getClassLoader();
        // 方法2.
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class<?> loadClass = classLoader.loadClass("com.codewsq.java.Person");
        System.out.println(loadClass);
    }


    //Class实例可以是哪些结构的说明：
    @Test
    public void test4(){
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要数组的元素类型与维度一样，就是同一个Class
        System.out.println(c10 == c11);

    }
}
